<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>客户端</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.9.1/vconsole.min.js"></script>
    <script>
        // init vConsole
        new VConsole();
        console.log('Hello world');
    </script>
    <style>
        video {
            margin: 20px;
        }
    </style>
</head>
<body>
<div id="video_area">
    <!--自己得视频 自己的视频静音-->
    <div>
        <video id="my" autoplay muted controls width="640" height="480"></video>
    </div>

</div>
</body>

<script type="text/javascript">
    //使用Google的stun服务器
    const iceServer = {
        "iceServers": [{
            "url": "stun:stun.l.google.com:19302"
        }, {
            "url": "turn:numb.viagenie.ca",
            "username": "webrtc@live.com",
            "credential": "muazkh"
        }]
    };

    //兼容浏览器的PeerConnection写法
    const PeerConnection = (window.PeerConnection ||
        window.webkitPeerConnection00 ||
        window.webkitRTCPeerConnection ||
        window.RTCPeerConnection ||
        window.mozRTCPeerConnection);

    /**
     * 信令通道
     * @type {WebSocket}
     */
    let websocket = null;
    /**
     * 本地音视频
     * @type {MediaStream}
     */
    let localAudioAndVideo = null;

    /**
     * 所有的已建立的对等连接
     */
    let peerConnectionAll = {}

    window.onload = async function () {
        // 获取本地媒体流
        const localAudioAndVideo = await getLocalMedia();
        if (localAudioAndVideo) {
            establishWebsocketConnection()
        } else {
            alert("无法获取流媒体资源")
        }
    }

    /**
     * 建立websocket连接
     */
    function establishWebsocketConnection() {

        const username = localStorage.getItem("username")
        if (username) {

            if ('WebSocket' in window) {
                websocket = new WebSocket("wss://101.37.152.195/");
                //连接发生错误的回调方法
                websocket.onerror = function (event) {
                    console.error("WebSocket error observed:", event);
                };

                //连接成功建立的回调方法
                websocket.onopen = function () {
                    console.log("连接成功建立的回调方法")
                    // 登录
                    const messageBody = {
                        instruct: 1,
                        data: {
                            username: username,
                            role: 1
                        }
                    }
                    websocket.send(JSON.stringify(messageBody))

                }

                //接收到消息的回调方法
                websocket.onmessage = function (event) {
                    const json = JSON.parse(event.data)
                    console.log(json);
                    if (json.instruct === 1) {
                        // 登录
                        const user = json.data.currentUser;
                        const attachableUser = json.data.attachableUser;
                        localStorage.setItem("user", JSON.stringify(user));

                        // 请求跟所有的客户端建立对等连接
                        if (attachableUser.length > 0) {
                            sendOffer(attachableUser);
                        }
                    } else if (json.instruct === 2) {
                        // 信令
                        const data = json.data;
                        if (data.event == "__offer") {
                            offerHandle(data);
                        } else if (data.event == "__answer") {
                            answerHandle(data)
                        } else if (data.event == "__ice_candidate") {
                            const pc = peerConnectionAll[data.sender.userId]
                            pc.connection.addIceCandidate(new RTCIceCandidate(data.data.candidate));
                        }
                    } else if (json.instruct === 3) {
                        // 退出

                    }
                }

                //连接关闭的回调方法
                websocket.onclose = function () {
                    console.log("连接关闭")
                }

                //监听窗口关闭事件，当窗口关闭时，主动去关闭websocket连接，防止连接还没断开就关闭窗口，server端会抛异常。
                window.onbeforeunload = function () {
                    websocket.close();
                }
            } else {
                alert('当前浏览器 Not support websocket')
            }
        } else {
            console.log("未登录")
        }
    }

    /**
     * 获取本地媒体资源
     */
    async function getLocalMedia() {

        const json = {
            "audio": false,
            "video": false
        }
        if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
            console.log("不支持 enumerateDevices() .");
            return null;
        } else {
            const devices = await navigator.mediaDevices.enumerateDevices()
            /**
             * audioinput 音频输入设备
             * audiooutput 音频输出
             * videoinput 视频输入
             */
            devices.forEach(d => {
                if (d.kind === 'audioinput') {
                    json.audio = true
                }
                if (d.kind === 'videoinput') {
                    json.video = true
                }
            })

            if (navigator.mediaDevices.getUserMedia) {
                //最新标准API
                //获取本地的媒体流，并绑定到一个video标签上输出，并且发送这个媒体流给其他客户端
                localAudioAndVideo = await navigator.mediaDevices.getUserMedia(json);
            } else if (navigator.webkitGetUserMedia) {
                //webkit内核浏览器
                localAudioAndVideo = await navigator.webkitGetUserMedia(json);
            } else {
                alert("不支持")
            }
            document.getElementById('my').srcObject = localAudioAndVideo
            return localAudioAndVideo;
        }
    }

    /**
     * 请求跟所有的客户端建立对等连接
     */
    function sendOffer(attachableUser) {
        attachableUser.forEach(json => {
            const pc = establishPC(json.userId);
            pc.createOffer().then(sdp => {
                pc.setLocalDescription(sdp);
                const messageBody = {
                    instruct: 2,
                    data: {
                        "recipient": json.userId,
                        "event": "__offer",
                        "data": {
                            "sdp": sdp
                        },
                    }
                }
                websocket.send(JSON.stringify(messageBody))
            })
            const peerConnection = {
                connection: pc,
                connectionUser: json
            }
            peerConnectionAll[json.userId] = peerConnection;

        })

    }

    /**
     * offer事件处理
     */
    function offerHandle(data) {
        // 创建对等连接
        const pc = establishPC(data.sender.userId);
        pc.setRemoteDescription(new RTCSessionDescription(data.data.sdp));
        // 回复应答
        pc.createAnswer().then(function (sdp) {
            pc.setLocalDescription(sdp);
            const messageBody = {
                instruct: 2,
                data: {
                    "recipient": data.sender.userId,
                    "event": "__answer",
                    "data": {
                        "sdp": sdp
                    },
                }
            }
            websocket.send(JSON.stringify(messageBody));
        });
        const peerConnection = {
            connection: pc,
            connectionUser: data.sender
        }
        peerConnectionAll[data.sender.userId] = peerConnection;
    }

    /**
     * answer 事件处理
     */
    function answerHandle(data) {
        // 创建对等连接
        const pc = peerConnectionAll[data.sender.userId].connection;
        pc.setRemoteDescription(new RTCSessionDescription(data.data.sdp));
    }

    function establishPC(userId) {
        // 创建对等连接
        const pc = new PeerConnection(iceServer);

        pc.onicecandidate = function (event) {
            if (event.candidate !== null && event.candidate !== undefined && event.candidate !== '') {
                const messageBody = {
                    instruct: 2,
                    data: {
                        "recipient": userId,
                        "event": "__ice_candidate",
                        "data": {
                            "candidate": event.candidate
                        },
                    }
                }
                websocket.send(JSON.stringify(messageBody));
            }
        };

        for (const track of localAudioAndVideo.getTracks()) {
            pc.addTrack(track, localAudioAndVideo);
        }

        //如果检测到媒体流连接到本地，将其绑定到一个video标签上输出
        pc.ontrack = function (event) {
            console.log(event)
            if (event.track.kind == 'audio') {
                if (event.streams && event.streams[0]) {
                    const video = document.createElement("video");
                    video.autoplay = true;
                    video.id = userId + "_audio";
                    video.srcObject = event.streams[0]
                    document.getElementById('video_area').appendChild(video);
                }
            } else if (event.track.kind == 'video') {
                if (event.streams && event.streams[0]) {
                    const video = document.createElement("video");
                    video.autoplay = true;
                    video.id = userId;
                    video.srcObject = event.streams[0]
                    document.getElementById('video_area').appendChild(video);
                }
            }

        };

        pc.oniceconnectionstatechange = function (event) {
            // 如果对等连接断开
            if (pc.iceConnectionState === "failed" ||
                pc.iceConnectionState === "disconnected" ||
                pc.iceConnectionState === "closed") {
                if (peerConnectionAll[userId]) {
                    delete peerConnectionAll[userId]
                }
                // 关闭播放组件
                document.getElementById(userId).remove()

            }
        };
        return pc;

    }
</script>
</html>